package com.gitee.wsl.mathematics.complex

import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.pow

private val PI_DIV_2 = Complex(PI / 2, 0)

val Complex.Companion.algebra: ComplexField get() = ComplexField

/**
 * A field of [Complex].
 */
 object ComplexField {

     val zero: Complex = 0.0.toComplex()
     val one: Complex = 1.0.toComplex()

     fun bindSymbolOrNull(value: String): Complex? = if (value == "i") i else null

    /**
     * The imaginary unit.
     */
     val i: Complex by lazy { Complex(0.0, 1.0) }

     fun number(value: Number): Complex = Complex(value.toDouble(), 0.0)

     fun scale(a: Complex, value: Double): Complex = Complex(a.re * value, a.im * value)

     fun add(left: Complex, right: Complex): Complex =
         Complex(left.re + right.re, left.im + right.im)
//     fun multiply(a: Complex, k: Number): Complex = Complex(a.re * k.toDouble(), a.im * k.toDouble())

//     fun Complex.minus(arg: Complex): Complex = Complex(re - arg.re, im - arg.im)

     fun multiply(left: Complex, right: Complex): Complex =
         Complex(left.re * right.re - left.im * right.im, left.re * right.im + left.im * right.re)

     fun divide(left: Complex, right: Complex): Complex = when {
        abs(right.im) < abs(right.re) -> {
            val wr = right.im / right.re
            val wd = right.re + wr * right.im

            if (wd.isNaN() || wd == 0.0)
                throw ArithmeticException("Division by zero or infinity")
            else
                Complex((left.re + left.im * wr) / wd, (left.im - left.re * wr) / wd)
        }

        right.im == 0.0 -> throw ArithmeticException("Division by zero")

        else -> {
            val wr = right.re / right.im
            val wd = right.im + wr * right.re

            if (wd.isNaN() || wd == 0.0)
                throw ArithmeticException("Division by zero or infinity")
            else
                Complex((left.re * wr + left.im) / wd, (left.im * wr - left.re) / wd)
        }
    }

     fun sin(arg: Complex): Complex = i * (exp(-i * arg) - exp(i * arg)) / 2.0
     fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2.0

     fun tan(arg: Complex): Complex {
        val e1 = exp(-i * arg)
        val e2 = exp(i * arg)
        return i * (e1 - e2) / (e1 + e2)
    }

     fun asin(arg: Complex): Complex = -i * ln(sqrt(1.0 - (arg * arg)) + i * arg)
     fun acos(arg: Complex): Complex = PI_DIV_2 + i * ln(sqrt(1.0 - (arg * arg)) + i * arg)

     fun atan(arg: Complex): Complex {
        val iArg = i * arg
        return i * (ln(1.0 - iArg) - ln(1.0 + iArg)) / 2
    }

     fun power(arg: Complex, pow: Number): Complex = if (arg.im == 0.0) {
        arg.re.pow(pow.toDouble()).toComplex()
    } else {
        exp(pow.toDouble() * ln(arg))
    }

     fun power(arg: Complex, pow: Complex): Complex = exp(pow * ln(arg))

     fun exp(arg: Complex): Complex = kotlin.math.exp(arg.re) * (kotlin.math.cos(arg.im) + i * kotlin.math.sin(
        arg.im
    ))

    fun ln(arg: Complex): Complex = kotlin.math.ln(arg.r) + i * atan2(arg.im, arg.re)

    fun norm(arg: Complex): Complex = sqrt(arg.conjugate * arg)

    /**
     * Computes the square root of the value [arg].
     */
    fun sqrt(arg: Complex): Complex = power(arg, 0.5)

    operator fun Complex.unaryMinus(): Complex = Complex(-re, -im)

    operator fun Complex.div(k: Number): Complex = Complex(re / k.toDouble(), im / k.toDouble())

    operator fun Complex.div(k: Complex): Complex = divide(this, k)

    /**
     * Adds complex number to real one.
     *
     * @receiver the augend.
     * @param c the addend.
     * @return the sum.
     */
     operator fun Double.plus(c: Complex): Complex = add(this.toComplex(), c)

    /**
     * Adds real number to complex one.
     *
     * @receiver the augend.
     * @param d the addend.
     * @return the sum.
     */
    operator fun Complex.plus(d: Double): Complex = d + this

    operator fun Complex.plus(d: Complex): Complex = add(this, d)

    /**
     * Subtracts complex number from real one.
     *
     * @receiver the minuend.
     * @param c the subtrahend.
     * @return the difference.
     */
    operator fun Double.minus(c: Complex): Complex = add(this.toComplex(), -c)

    /**
     * Subtracts real number from complex one.
     *
     * @receiver the minuend.
     * @param d the subtrahend.
     * @return the difference.
     */
    operator fun Complex.minus(d: Double): Complex = add(this, -d.toComplex())

    operator fun Complex.minus(d: Complex): Complex = add(this, -d)

    /**
     * Multiplies real number by complex one.
     *
     * @receiver the multiplier.
     * @param c the multiplicand.
     * @receiver the product.
     */
     operator fun Double.times(c: Complex): Complex = Complex(c.re * this, c.im * this)

     operator fun Complex.times(arg: Number): Complex = arg.toDouble() * this

     operator fun Complex.times(arg: Complex): Complex = multiply(this, arg)
}